﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using BattleTank3D.Enums;
using BattleTank3D.Obstaculo;

namespace BattleTank3D
{
    class Jogo
    {
        //Faz os desenhos 2D na tela
        SpriteBatch spriteBatch;

        //Lista de inimigos
        List<Inimigo> listaInimigos = new List<Inimigo>();

        // Lista de inimigos que irão se colidir
        List<BoundingSphere> listaInimigosBoudingSphere = new List<BoundingSphere>();

        //Lista de Obstaculo
        List<Cubo> listaCubos = new List<Cubo>();
        List<Piramide_3B> listaP3B = new List<Piramide_3B>();
        List<Piramide_4B> listaP4B = new List<Piramide_4B>();

        // Lista de Obstaculos que irão se colidir
        BoundingBox[] obstaculosBoundingBoxes;

        // O Deserto inteiro
        BoundingBox desertoCompletoBox;

              
        

        //Controle do XBOX
        GamePadState gamePad = GamePad.GetState(PlayerIndex.One);

        //Modelo do jogador --- Deve ser movido para a classe Tank e subsituido por uma chamada a essa classe
        Tank tank;
                
        //Controla se os objetos serão desenhados sólidos ou em wireframe
        RasterizerState solid = new RasterizerState();
        RasterizerState wired = new RasterizerState();

        //Música durante o jogo e seu controle de execução
        Song musicaJogo;
        bool tocandoMusica = false;
        
        /// <summary>
        /// viewport do mapa, deve mostrar a cena toda
        /// </summary>
        Viewport mapViewport;

        /// <summary>
        /// Fonte do texto utilizada nos creditos
        /// </summary>
        SpriteFont fonteExplicacaoJogo;

        SpriteFont fontePause;

        //Fundo de tela do jogo
        SkySphere sky;

        //Dados da partida
        int pontuacao = 0, vidas = 5;

        /// <summary>
        /// imagem da tela
        /// </summary>
        Texture2D imagemPause, HUD;

       

        public Jogo(SpriteBatch sprbatch)
        {
            this.spriteBatch = sprbatch;
        }

        /// <summary>
        /// Carrega as variaveis do jogo
        /// </summary>
        public void Initialize()
        {
            
        }

        /// <summary>
        /// Carrega o conteudo externo do jogo, como musica, imagens, fontes de texto, etc.
        /// </summary>
        /// <param name="GraphicsDevice">Objeto que contem as definições de video do objeto Principal</param>
        /// <param name="content">Objeto que contem a referencia da localização do conteúdo externo do objeto Principal</param>
        public void LoadContent(GraphicsDevice GraphicsDevice, ContentManager content, Viewport defaultViewport)
        {
            // Trilha Sonora
            musicaJogo = content.Load<Song>("Música/Metallica_Metal_Militia");

            MediaPlayer.IsRepeating = true;

            imagemPause = content.Load<Texture2D>("Telas/pause");
            HUD = content.Load<Texture2D>("Imagens/Planos_de_Fundo/HUD");

           
            

            Random random = new Random(DateTime.Now.Millisecond);
            for (int i = 0; i < 50; i++) {
                float x = random.Next(0, 1000);
                float z = random.Next(0, 1000);
                float r = random.Next(0, 10);
               
                // Base para criação de todos os obstáculos do jogo
                Vector3 position = new Vector3(x*2, 0, z*2);
                Cubo obstaculo_cubo = new Cubo(content.Load<Model>("Modelos/Cubo"), position, GraphicsDevice);
                Piramide_3B piramide_P3B = new Piramide_3B(content.Load<Model>("Modelos/Piramide_3B"), position, GraphicsDevice);
                Piramide_4B piramide_P4B = new Piramide_4B(content.Load<Model>("Modelos/Piramide_4B"), position, GraphicsDevice);

                obstaculo_cubo.rotation = MathHelper.ToRadians(r*36);
                piramide_P3B.rotation = MathHelper.ToRadians(r * 36);
                piramide_P4B.rotation = MathHelper.ToRadians(r * 36);

                listaCubos.Add(obstaculo_cubo);
                listaP3B.Add(piramide_P3B);
                listaP4B.Add(piramide_P4B);
            }

            // Prepara as pedras para colidirem
            ConfiguraBoudingBox();
            AdicionaAlvo(content, "Modelos/Tank_Atari",20);
            AdicionaAlvo(content, "Modelos/Disco_Voador", 2);

            //Inimigo inimigo = new Inimigo();
            
            //inimigo.setPosition(new Vector3(0, 0, 10));
            //inimigo.model = Content.Load<Model>("Modelos/Disco_Voador");
            //listaInimigos.Add(inimigo);

            ////float raioInimigo = 1000 / 1000f * 0.2f + 0.01f;
            //float raioInimigo = 0.0794f;
            //BoundingSphere alvoInimigo = new BoundingSphere(inimigo.getPosition(), raioInimigo);

            //if (VerificaColisao(alvoInimigo) == TipoColisao.NENHUM)
            //{
            //    listaInimigosBoudingSphere.Add(alvoInimigo);
            //}

            //Inimigo inimigo2 = new Inimigo();
            //inimigo2.setPosition(new Vector3(20, 0, 10));
            //inimigo2.model = Content.Load<Model>("Modelos/Tank_Atari");
            //listaInimigos.Add(inimigo2);

            //BoundingSphere alvoInimigo2 = new BoundingSphere(inimigo2.getPosition(), raioInimigo);
            //if (VerificaColisao(alvoInimigo2) == TipoColisao.NENHUM)
            //{
            //    listaInimigosBoudingSphere.Add(alvoInimigo2);
            //}

            
            fonteExplicacaoJogo = content.Load<SpriteFont>("Fonts/explicacaoJogoFont");
            fontePause = content.Load<SpriteFont>("Fonts/menuFont");

            wired.FillMode = FillMode.WireFrame;
            solid.FillMode = FillMode.Solid;

            // Definição do Viewport principal e secundário
            mapViewport = defaultViewport;
            mapViewport.Width = defaultViewport.Width/4;
            mapViewport.Height = defaultViewport.Height/4;
            mapViewport.X = (defaultViewport.Width/2) - mapViewport.Width/2;
            mapViewport.Y = 0;

            tank = new Tank(defaultViewport, mapViewport);
            tank.Load(content);
            
            sky = new SkySphere(content, GraphicsDevice,
                content.Load<TextureCube>("Imagens/Planos_de_Fundo/Black_sky"));
                        
            
        }

        /// <summary>
        /// Metodo que irá atualizar o estado dos objetos no jogo
        /// </summary>
        /// <param name="teclaPressionada">Objeto que contem o evento capturado pelo teclado</param>
        /// <param name="teclaAnteriorPressionada">Objeto que contem o ultimo evento capturado pelo teclado</param>
        /// <param name="graphicsDevice">Objeto que contem as definições de video do objeto Principal</param>
        /// <param name="gameTime">Objeto com o tempo do jogo, utilizado para a apresentação dos tiros</param>
        public void Update(KeyboardState teclaPressionada, KeyboardState teclaAnteriorPressionada, GraphicsDevice graphicsDevice, GameTime gameTime)
        {
            gamePad = GamePad.GetState(PlayerIndex.One);

            if (gamePad.DPad.Up == ButtonState.Pressed)
            {
                Console.WriteLine(gamePad.ThumbSticks.Left.X);
            }

            //O Gamepad tbm ja esta configurado na classe camera
            if (teclaPressionada.IsKeyDown(Keys.X) && !(teclaAnteriorPressionada.IsKeyDown(Keys.X) && 
                !(gamePad.DPad.Up == ButtonState.Pressed)))
            {
                graphicsDevice.RasterizerState = solid;
            }
             
            if (teclaPressionada.IsKeyDown(Keys.Z) && !(teclaAnteriorPressionada.IsKeyDown(Keys.Z) && 
                ! (gamePad.DPad.Down == ButtonState.Pressed)))
            {
                graphicsDevice.RasterizerState = wired;                
            }
            
            if (!tocandoMusica) // Tocar música
            {
                MediaPlayer.Play(musicaJogo);
                tocandoMusica = true;
            }

            tank.Update(gameTime);


           
           
            foreach (Inimigo inimigo in listaInimigos) // Loop through List with foreach
            {
                inimigo.Update(tank.getPosition());
            }

            float raioTank = 0.04f;
            BoundingSphere tankSphere = new BoundingSphere(tank.getPosition(), raioTank);
            // Existe um problema de posicionamento da camera em relação aos objetos
            // Esse problema prejudica a detecção da colisão
            // O posicionamento da camera em relação aos objetos deve ser revisto.
            if (VerificaColisao(tankSphere) != TipoColisao.NENHUM)
            {
                tank.reset();

            }

            
        }

        private static Vector3 handleGamePadSlide(GamePadState gamepadState)
        {
            return new Vector3(
                gamepadState.ThumbSticks.Right.X, 0,
                -gamepadState.ThumbSticks.Right.Y);
        }

        /// <summary>
        /// Metodo que trata a apresentação grafica do jogo
        /// </summary>
        /// <param name="gameTime">Objeto que contem o "tempo do jogo"</param>
        /// <param name="graphicsDevice">Objeto que contem as definições de video do objeto Principal</param>
        /// <param name="defaultViewport">utilizado para apresentar os objetos no radar do HUD</param>
        public void Draw(GameTime gameTime, GraphicsDevice graphicsDevice, Viewport defaultViewport)
        {
            // DESENHAR AQUI TUDO O QUE FOR RELATIVO A TELA DE JOGO
           DrawTelaJogo(graphicsDevice, defaultViewport);   
           
            // DESENHAR AQUI TUDO O QUE FOR RELATIVO AO MAPA
            DrawMapaJogo(graphicsDevice, defaultViewport);
           
            //DESENHAR AQUI TUDO O QUE FOR RELATIVO AO HUD
            DrawHUD(graphicsDevice, defaultViewport);
        }

        /// <summary>
        /// Toda a apresentação gráfica relativa ao jogo
        /// </summary>
        /// <param name="dispositivoGrafico">Objeto que contem as definições de video do objeto Principal</param>
        /// <param name="defaultViewport">utilizado para apresentar os objetos no radar do HUD</param>
        private void DrawTelaJogo(GraphicsDevice dispositivoGrafico, Viewport defaultViewport)
        {
            //GraphicsDevice.RasterizerState = wired;
            dispositivoGrafico.RasterizerState = solid;

            dispositivoGrafico.Viewport = defaultViewport;


            sky.Draw(tank.getView(), tank.getProj(), tank.getPosition(), new Vector3(200f));

            dispositivoGrafico.RasterizerState = wired;
            Vector3 pos = new Vector3(0, 0, 10);
            foreach (Inimigo inimigo in listaInimigos) // Loop through List with foreach
            {
                inimigo.Draw(tank.getView(), tank.getProj());
            }

            //tank.Draw(camera.getWorld(), camera.getView(), camera.getProj());
            //desenha os objectos
            foreach (Cubo obstaculo in listaCubos)
            {
                obstaculo.Draw(tank.getView(), tank.getProj());
            }

            foreach (Piramide_3B obstaculo in listaP3B)
            {
                obstaculo.Draw(tank.getView(), tank.getProj());
            }

            foreach (Piramide_4B obstaculo in listaP4B)
            {
                obstaculo.Draw(tank.getView(), tank.getProj());
            }

            tank.Draw(dispositivoGrafico);    
        }

        /// <summary>
        /// Toda a apresentação gráfica relativa ao radar do jogador
        /// </summary>
        /// <param name="graphicsDevice">Objeto que contem as definições de video do objeto Principal</param>
        /// <param name="defaultViewport">utilizado para apresentar os objetos no radar do HUD</param>
        private void DrawMapaJogo(GraphicsDevice graphicsDevice, Viewport defaultViewport)
        {
            graphicsDevice.Viewport = mapViewport;
            graphicsDevice.RasterizerState = solid;

            //troca a camera para o modo mapa
            tank.DrawMap();

            //desenha todos os inimigos no mapa
            foreach (Inimigo inimigo in listaInimigos) // Loop through List with foreach
            {
                inimigo.Draw(tank.getMapView(), tank.getMapProj());
            }
            //desenha os objectos
            foreach (Cubo obstaculo in listaCubos)
            {
                obstaculo.Draw(tank.getMapView(), tank.getMapProj());
            }

            foreach (Piramide_3B obstaculo in listaP3B)
            {
                obstaculo.Draw(tank.getMapView(), tank.getMapProj());
            }

            foreach (Piramide_4B obstaculo in listaP4B)
            {
                obstaculo.Draw(tank.getMapView(), tank.getMapProj());
            }

        }

        /// <summary>
        /// Toda a apresentação gráfica relativa a tela principal do jogador
        /// </summary>
        /// <param name="graphicsDevice">Objeto que contem as definições de video do objeto Principal</param>
        /// <param name="defaultViewport">utilizado para apresentar os objetos no radar do HUD</param>
        private void DrawHUD(GraphicsDevice graphicsDevice, Viewport defaultViewport)
        {

            graphicsDevice.Viewport = defaultViewport;

            // Exibição do HUD
            String HUDscore = " Score: " +
                         pontuacao +
                         "\n Vidas: " +
                         vidas;
            spriteBatch.Begin();
            spriteBatch.DrawString(fonteExplicacaoJogo, HUDscore, Vector2.Zero, Color.Green);
            spriteBatch.Draw(HUD, Vector2.Zero, Color.White);

            spriteBatch.End();
        }

        /// <summary>
        /// Desenha o PAUSE na tela
        /// </summary>
        /// <param name="spriteBatch">Objeto do tipo SpriteBatch</param>
        /// <param name="largura">largura da tela</param>
        /// <param name="altura">altura da tela</param>
        public void DrawPause(SpriteBatch spriteBatch, int largura, int altura)
        {
            spriteBatch.Begin();
            spriteBatch.Draw(imagemPause, Vector2.Zero, new Color(100, 100, 100, 100));
            spriteBatch.DrawString(fontePause, "PAUSE",
                new Vector2(
                    largura - largura/ 2
                    - fontePause.MeasureString("PAUSE").X / 2,
                    altura - altura / 2
                    - fontePause.MeasureString("PAUSE").Y / 2),
                    Color.DarkRed);
            
            spriteBatch.End();
        }

        private void ConfiguraBoudingBox()
        {
            int larguraDeserto = 1000;
            int comprimentoDeserto = 1000;


            List<BoundingBox> bbList = new List<BoundingBox>();

            for (int i = 0; i < listaCubos.Count; i++)
            {
                Cubo obstaculo = listaCubos[i];
                int alturaObstaculo = 5;
                Vector3[] buildingPoints = new Vector3[2];
                buildingPoints[0] = new Vector3(obstaculo.position.X, 0, -obstaculo.position.Z);
                buildingPoints[1] = new Vector3(obstaculo.position.X + 1, alturaObstaculo, -obstaculo.position.Z - 1);
                BoundingBox buildingBox = BoundingBox.CreateFromPoints(buildingPoints);
                bbList.Add(buildingBox);
            }

            
            obstaculosBoundingBoxes = bbList.ToArray();

            Vector3[] boundaryPoints = new Vector3[2];
            boundaryPoints[0] = new Vector3(0, 0, 0);
            boundaryPoints[1] = new Vector3(larguraDeserto, 20, -comprimentoDeserto);

            desertoCompletoBox = BoundingBox.CreateFromPoints(boundaryPoints);
        }

        /// <summary>
        /// Verifica se houve colisão
        /// </summary>
        /// <param name="sphere">objeto relacionado ao jogador</param>
        /// <param name="listaInimigosBoudingSphere">objeto relacionado aos inimigos na tela</param>
        /// <returns>Tipo da Colisao</returns>
        private TipoColisao VerificaColisao(BoundingSphere sphere)
        {

            for (int j = 0; j < obstaculosBoundingBoxes.Length; j++)
            {
                if (obstaculosBoundingBoxes[j].Contains(sphere) != ContainmentType.Disjoint)
                {
                    return TipoColisao.PEDRA;
                }
            }

            for (int i = 0; i < listaInimigosBoudingSphere.Count; i++)
            {
                if (listaInimigosBoudingSphere[i].Contains(sphere) != ContainmentType.Disjoint)
                {
                    listaInimigosBoudingSphere.RemoveAt(i);
                    i--;

                    return TipoColisao.INIMIGO;
                }
            }

            return TipoColisao.NENHUM;
        }

        /// <summary>
        /// Adiciona os adversarios na Tela
        /// </summary>
        /// <param name="Content">Objeto que contem a referencia da localização do conteúdo externo do objeto Principal</param>
        /// <param name="modeloInimigo">Objeto String que contem o caminho do modelo que deve ser usado para o inimigo</param>
        /// <param name="qtdMaxima">inteiro com a quantidade maxima que esse modelo deve ter na tela</param>
        private void AdicionaAlvo(ContentManager Content, String modeloInimigo, int qtdMaxima)
        {
            int larguraDeserto = 1000;
            int comprimentoDeserto = 1000;

            Random random = new Random();

            
            while (listaInimigos.Count < qtdMaxima)
            {
                int x = random.Next(larguraDeserto);
                int z = -random.Next(comprimentoDeserto);
                
                Inimigo inimigo = new Inimigo();

                inimigo.setPosition(new Vector3(x, 0, z));
                inimigo.model = Content.Load<Model>(modeloInimigo);
                listaInimigos.Add(inimigo);

                //float raioInimigo = 1000 / 1000f * 0.2f + 0.01f;
                float raioInimigo = 0.0794f;
                BoundingSphere alvoInimigo = new BoundingSphere(inimigo.getPosition(), raioInimigo);

                if (VerificaColisao(alvoInimigo) == TipoColisao.NENHUM)
                {
                    listaInimigosBoudingSphere.Add(alvoInimigo);
                    listaInimigos.Add(inimigo);
                }

                
            }
        }

       
    }
}
